// import_obj.cpp : Defines the entry point for the console application.
// Converts from OBJ to own format
// IMPORTANT: RHS/CCW (obj) to LHS/CW (our format)

#include "stdafx.h"

#define EXIT_OK		0
#define EXIT_ERROR	-1
#define OBJFILE_MAXLINELENGHT 256

// Typedefs
typedef struct
{
	float x, y, z;
} TVertex;

typedef struct
{
	float u, v;
} TTexCoord;

typedef struct
{
	int vIndex1, vIndex2, vindex3;
	int tIndex1, tIndex2, tIndex3;
	int nIndex1, nIndex2, nIndex3;
} TFace;

// Functions
void getModelFilename(char *);
bool readFileCounts(std::stringstream &, int &, int &, int &, int &);
bool loadDataStructures(std::stringstream &, int, int, int, int);

// Main program

// @ TODO: Convert into a class, used by test_DX_01 when no converted model exists
int _tmain(int argc, _TCHAR* argv[])
{
	char filename[256];
	getModelFilename(filename);

	int vertexCount = 0, texCoordCount = 0, normalCount = 0, faceCount = 0;

		// Input file
	std::ifstream fin(filename);
	if(!fin.good())
	{
		std::cerr << "Error opening " << filename << std::cerr;
		return false;
	}

	fin.seekg(0, std::ios::end);
	std::streamoff fileLength = fin.tellg();
	fin.seekg(0, std::ios::beg);
	
	if(fin.fail() || fileLength < 0)
	{
		std::cerr << "Error getting file size" << std::endl;
		return false;
	}

	char * buffer = new char[(unsigned int)fileLength + 1];
	if(!buffer)
	{
		std::cerr << "Error allocating buffer (first time)" << std::endl;
		return false;
	}
	memset(buffer, 0, sizeof(char) * (size_t)(fileLength + 1));
	
	fin.read(buffer, fileLength);
	if(fin.eof() && fileLength > fin.gcount() + 1)
	{
		// EOF reached before "fileLenght", update it
		fileLength = fin.gcount();
	}

	// Add string terminator at final position
	buffer[fileLength] = '\0'; // Needed? Already zeroed with memset

	std::stringstream ss(buffer);
	delete[] buffer;
	buffer = NULL;

	if (!readFileCounts(ss, vertexCount, texCoordCount, normalCount, faceCount))
	{
		std::cerr << "Error counting stuff at file " << filename << std::endl;
		return EXIT_ERROR;
	}

	// Display counts
	std::cout << "Verts: " << vertexCount
		<< "\nTexCoords: " << texCoordCount
		<< "\nNormals: " << normalCount
		<< "\nFaces: " << faceCount << std::endl;

	// Clear stream state
	ss.clear();
	// Rewind ss
	ss.seekg(0, std::ios::beg);

	if (!loadDataStructures(ss, vertexCount, texCoordCount, normalCount, faceCount))
	{
		std::cerr << "Error loading data structures from " << filename << std::endl;
		return EXIT_ERROR;
	}

	std::cout << "File converted" << std::endl;

	return EXIT_OK;
}

void getModelFilename(char * filename)
{
	std::ifstream fin;
	bool done = false;
	while (!done)
	{
		std::cout << "Enter model filename: ";
		// @ TODO: No control of filename max length!!
		std::cin >> filename;

		fin.open(filename);
		if (fin.good())
		{
			done = true;
		}
		else
		{
			fin.clear();
			std::cout << filename << " could not be opened" << std::endl;
		}
	}
}

bool readFileCounts(std::stringstream & ss,
					int & vCount,
					int & tCount,
					int & nCount,
					int & fCount)
{
	// Init counts
	vCount = tCount = nCount = fCount = 0;
	char buffer[OBJFILE_MAXLINELENGHT];
	memset(buffer, 0, sizeof(char) * OBJFILE_MAXLINELENGHT);

	unsigned pos = 0;
	ss.getline(buffer, OBJFILE_MAXLINELENGHT);
	while(!ss.bad() && !ss.eof())
	{
		if(ss.fail())
		{
			std::cerr << "Line too long: " << buffer << std::endl;
			return false;
		}

		switch (buffer[pos++])
		{
		case 'v':
			switch (buffer[pos])
			{
			case ' ': // v
				++vCount;
				break;
			case 'n': // vn
				++nCount;
				break;
			case 't': // vt
				++tCount;
				break;
			}
			break;
		case 'f':
			++fCount;
			break;
		}

		// Get new line
		pos = 0;
		ss.getline(buffer, OBJFILE_MAXLINELENGHT);
	}

	return true;
}

bool loadDataStructures(std::stringstream & ss,
						int vCount,
						int tCount,
						int nCount,
						int fCount)
{
	TVertex * vertices = new TVertex[vCount];
	if (!vertices)
	{
		std::cerr << "Error allocating memory for vertices" << std::endl;
		return false;
	}
	memset(vertices, 0, sizeof(TVertex) * vCount);

	TVertex * normals = new TVertex[nCount];
	if (!normals)
	{
		std::cerr << "Error allocating memory for normals" << std::endl;
		return false;
	}
	memset(normals, 0, sizeof(TVertex) * nCount);

	TTexCoord * texCoords = new TTexCoord[tCount];
	if(!texCoords)
	{
		std::cerr << "Error allocating memory for texture coordinates" << std::endl;
		return false;
	}
	memset(texCoords, 0, sizeof(TTexCoord) * tCount);

	char * buffer = new char[OBJFILE_MAXLINELENGHT];
	if(!buffer)
	{
		std::cerr << "Error allocating buffer (second time)" << std::endl;
		return false;
	}
	memset(buffer, 0, sizeof(char) * OBJFILE_MAXLINELENGHT);

	// Output file
	std::ofstream fout("data\\model.txt", std::ios::trunc);
	if(!fout.good())
	{
		std::cerr << "Error opening file data\\model.txt" << std::endl;
		return false;
	}

	// Write file header:
	fout << fCount * 3 << '\n';

	// More variables
	unsigned vs = 0, ts = 0, ns = 0; // vertex/texture/normal counter
	unsigned vIndex1 = 0, vIndex2 = 0, vIndex3 = 0; // vertex indices at a certain face
	unsigned tIndex1 = 0, tIndex2 = 0, tIndex3 = 0; // texture indices at a certain face
	unsigned nIndex1 = 0, nIndex2 = 0, nIndex3 = 0; // normal indices at a certain face
	
	unsigned pos = 0; // position inside buffer
	ss.getline(buffer, OBJFILE_MAXLINELENGHT);
	while(!ss.bad() && !ss.eof())
	{
		if(ss.fail())
		{
			std::cerr << "Line too long: " << buffer << std::endl;
			return false;
		}

		switch(buffer[pos++])
		{
		case 'v':
			switch(buffer[pos++])
			{
			case ' ':
				// Vertex
				std::stringstream(&(buffer[pos]))
					>> vertices[vs].x >> vertices[vs].y >> vertices[vs].z;
				vertices[vs].z *= -1.0f; // Convert from RHS (obj) to LHS
				++vs;
				break;

			case 'n':
				// Normal
				std::stringstream(&(buffer[pos]))
					>> normals[ns].x >> normals[ns].y >> normals[ns].z;
				normals[ns].z *= -1.0f; // Convert from RHS (obj) to LHS
				++ns;
				break;
			case 't':
				// Texture
				std::stringstream(&(buffer[pos]))
					>> texCoords[ts].u >> texCoords[ts].v;
				++ts;
				break;
			}
			break;
		case 'f':
			// Face

			// Code piece executed once to check face format
			static bool bFaceCheck = false, bNoTexture = false;
			if (!bFaceCheck)
			{
				std::stringstream ss(&(buffer[pos]));
				// Ignore up to '/'
				ss.ignore(std::numeric_limits<std::streamsize>::max(), '/');

				// If we reach EOF, there's somethign wrong
				if (ss.eof())
				{
					std::cerr << "Error at OBJ file: Unexpected EOF when checking face format"
						<< std::endl;

					return false;
				}

				if(ss.get() == '/')
				{
					bNoTexture = true;
				}

				bFaceCheck = true;
			}

			char slash; // Used to read the '/' delimiter

			// We convert drawing order from CCW (obj) to CW, reading indexes in reverse order
			if (bNoTexture)
			{
				std::stringstream(&(buffer[pos]))
					>> vIndex1 >> slash >> slash >> nIndex1
					>> vIndex3 >> slash >> slash  >> nIndex3
					>> vIndex2 >> slash >> slash >> nIndex2;
			}
			else
			{
				std::stringstream(&(buffer[pos]))
					>> vIndex1 >> slash >> tIndex1 >> slash >> nIndex1
					>> vIndex3 >> slash  >> tIndex3 >> slash  >> nIndex3
					>> vIndex2 >> slash  >> tIndex2 >> slash >> nIndex2;
			}

			// Decrement indices. OBJ starts counting from 1
			--vIndex1; --vIndex2; --vIndex3;
			if (!bNoTexture)
			{
				--tIndex1; --tIndex2; --tIndex3;
			}
			--nIndex1; --nIndex2; --nIndex3;

			// Write vertex/normal data to fout. One face at a a time
			// Face's first vertex --------------------------
			fout << vertices[vIndex1].x << ' '
				<< vertices[vIndex1].y << ' '
				<< vertices[vIndex1].z << ' ';
			if(bNoTexture)
			{
				fout << 0.0f  << ' ' << 0.0f << ' '; // Texture coords, unused
			}
			else
			{
				fout << texCoords[tIndex1].u << ' '
					<< texCoords[tIndex1].v << ' ';
			}
			fout << normals[nIndex1].x << ' '
				<< normals[nIndex1].y << ' '
				<< normals[nIndex1].z << '\n';

			// Face's second vertex --------------------------
			fout << vertices[vIndex2].x << ' '
				<< vertices[vIndex2].y << ' '
				<< vertices[vIndex2].z << ' ';
			if(bNoTexture)
			{
				fout << 0.0f  << ' ' << 0.0f << ' '; // Texture coords unused
			}
			else
			{
				fout << texCoords[tIndex2].u << ' '
					<< texCoords[tIndex2].v << ' ';
			}
			fout << normals[nIndex2].x << ' '
				<< normals[nIndex2].y << ' '
				<< normals[nIndex2].z << '\n';

			// Face's third vertex --------------------------
			fout << vertices[vIndex3].x << ' '
				<< vertices[vIndex3].y << ' '
				<< vertices[vIndex3].z << ' ';
			if(bNoTexture)
			{
				fout << 0.0f  << ' ' << 0.0f << ' '; // Texture coords unused
			}
			else
			{
				fout << texCoords[tIndex3].u << ' '
					<< texCoords[tIndex3].v << ' ';
			}
			fout << normals[nIndex3].x << ' '
				<< normals[nIndex3].y << ' '
				<< normals[nIndex3].z << '\n';

			break;
		}

		// Read new line
		ss.getline(buffer, OBJFILE_MAXLINELENGHT);
		pos = 0;
	}

	fout.flush();
	if(fout.bad())
	{
		std::cerr << "Error writing to output file" << std::endl;
	}

	fout.close();

	return true;
}
